package com.gupaowork.pattern.proxy.dynamic;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author heweicheng
 * 自定义代理
 */
public class YiyanProxy {

    public static final String ln = "\r\n";

    public static AtomicInteger count = new AtomicInteger();

    public static ThreadLocal<String> className = new ThreadLocal<String>();

    private static final String classNamePrefix = "$Proxy";

    private static final String fileNameSubfix = ".java";

    public static Object newProxyInstance(YiyanClassLoader classLoader, Class<?>[] interfaces, YiyanInvocationHandler h) {
        try {

            className.set(classNamePrefix + count.getAndIncrement());
            //1、动态生成源代码.java文件
            String src = generateSrc(interfaces);

            System.out.println(src);
            //2、Java文件输出磁盘
            String filePath = YiyanProxy.class.getResource("").getPath();
//           System.out.println(filePath);
            File f = new File(filePath + className.get() + fileNameSubfix);
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();

            //3、把生成的.java文件编译成.class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manage.getJavaFileObjects(f);

            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
            task.call();
            manage.close();

            //4、编译生成的.class文件加载到JVM中来
            Class proxyClass = classLoader.findClass(className.get());
            Constructor c = proxyClass.getConstructor(YiyanInvocationHandler.class);
            f.delete();
            className.remove();
            //5、返回字节码重组以后的新的代理对象
            return c.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String generateSrc(Class<?>[] interfaces) {
        StringBuffer sb = new StringBuffer();
        System.out.println(interfaces[0].getName());
        sb.append("package com.gupaowork.pattern.proxy.dynamic;" + ln);
        StringBuilder implementClasses = new StringBuilder();
        for (Class clazz : interfaces) {
            sb.append("import " + clazz.getName() + ";" + ln);
            implementClasses.append(clazz.getName()).append(",");
        }
        sb.append("import java.lang.reflect.*;" + ln);
        sb.append("public class " + className.get() + " implements " + implementClasses.substring(0, implementClasses.length() - 1) + "{" + ln);
        sb.append("YiyanInvocationHandler h;" + ln);
        sb.append("public " + className.get() + "(YiyanInvocationHandler h) { " + ln);
        sb.append("this.h = h;");
        sb.append("}" + ln);
        for (Class clazzs : interfaces) {
            for (Method m : clazzs.getMethods()) {
                Class<?>[] params = m.getParameterTypes();

                StringBuffer paramNames = new StringBuffer();
                StringBuffer paramValues = new StringBuffer();
                StringBuffer paramClasses = new StringBuffer();

                for (int i = 0; i < params.length; i++) {
                    Class clazz = params[i];
                    String type = clazz.getName();
                    String paramName = toLowerFirstCase(clazz.getSimpleName());
                    paramNames.append(type + " " + paramName);
                    paramValues.append(paramName);
                    paramClasses.append(clazz.getName() + ".class");
                    if (i > 0 && i < params.length - 1) {
                        paramNames.append(",");
                        paramClasses.append(",");
                        paramValues.append(",");
                    }
                }

                sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramNames.toString() + ") {" + ln);
                sb.append("try{" + ln);
                sb.append("Method m = " + clazzs.getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
                sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})", m.getReturnType()) + ";" + ln);
                sb.append("}catch(Error _ex) { }");
                sb.append("catch(Throwable e){" + ln);
                sb.append("throw new UndeclaredThrowableException(e);" + ln);
                sb.append("}");
                sb.append(getReturnEmptyCode(m.getReturnType()));
                sb.append("}");
            }
        }
        sb.append("}" + ln);
        return sb.toString();
    }


    private static Map<Class, Class> mappings = new HashMap<Class, Class>();

    static {
        mappings.put(int.class, Integer.class);
    }

    private static String getReturnEmptyCode(Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "return 0;";
        } else if (returnClass == void.class) {
            return "";
        } else {
            return "return null;";
        }
    }

    private static String getCaseCode(String code, Class<?> returnClass) {
        if (mappings.containsKey(returnClass)) {
            return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
        return code;
    }

    private static boolean hasReturnValue(Class<?> clazz) {
        return clazz != void.class;
    }

    private static String toLowerFirstCase(String src) {
        char[] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}
